package org.carabiner.infinitest;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;

import org.carabiner.infinitest.depgraph.DependencyGraph;
import org.carabiner.infinitest.depgraph.JavaClass;
import org.carabiner.infinitest.depgraph.RegexVerifier;
import org.carabiner.infinitest.depgraph.TestVerifier;

public class InfinitestMain extends TimerTask
{  
  private Color workingColor;
  private Timer timer;
  private DependencyGraph index;  
  private TestRunner runner;
  private ResultTreeModel testResults;
  private JTree tree;
  private JFrame frame;
  private boolean runTests = true;
  private File filterFile = new File("infinitest.filters");

  public InfinitestMain(File path) throws IOException
  {    
    frame = new JFrame();    
    frame.setAlwaysOnTop(true);
    frame.setLayout(new BorderLayout());
    testResults = new ResultTreeModel();
    tree = createTree();
    workingColor = tree.getBackground();
    testResults.addTreeModelListener(new TreeModelExpansionListener(tree));
    frame.add(new JScrollPane(tree), BorderLayout.CENTER);    
    runner = new JUnitRunner();
    runner.addTestStatusListener(new EventNormalizer(testResults));
    runner.addTestStatusListener(new EventNormalizer(new FrameUpdater(frame, tree)));
    TestVerifier verifier = new RegexVerifier(getFilterList(filterFile));
    index = new DependencyGraph(path, verifier);    
    timer = new Timer();
    frame.add(createToolBar(), BorderLayout.WEST);
    frame.setTitle(InfinitestSettings.SCANNING_MSG);
    frame.setSize(480, 120);    
    timer.schedule(this, 100, InfinitestSettings.getRefreshRate());    
  }

  List<Pattern> getFilterList(File file) throws IOException
  {
    List<Pattern> filterList = new ArrayList<Pattern>();
    if (file.exists())
    {
      FileReader fileReader = new FileReader(file);
      try
      {        
        BufferedReader reader = new BufferedReader(fileReader);
        String line = null;
        do
        {
        line = reader.readLine();
        if (line != null)
        {
          boolean isComment = line.startsWith("!") || line.startsWith("#");
          if (!isComment)
            filterList.add(Pattern.compile(line));
        }
        }while(line != null);
      } finally
      {
        fileReader.close();
      }
    }
    return filterList;
  }

  private Component createToolBar()
  {
    JToolBar toolbar = new JToolBar(JToolBar.VERTICAL);    
    toolbar.add(new ReloadIndexAction(index));    
    toolbar.add(new HaltTestAction(this));
    return toolbar;
  }

  private JTree createTree()
  {
    JTree resultsTree = new ResultsTree(testResults);
    resultsTree.setRootVisible(false);
    resultsTree.setEditable(false);
    resultsTree.setShowsRootHandles(true);
    resultsTree.setCellRenderer(new FailureCellRenderer());
    ToolTipManager tipManager = ToolTipManager.sharedInstance();
    tipManager.registerComponent(resultsTree);
    tipManager.setDismissDelay(10000);
    resultsTree.addMouseListener(new ClipboardCopyListener());
    return resultsTree;
  }
  
  private URL[] toURLs(File[] path) throws MalformedURLException
  {
    URL[] buildPath = new URL[path.length];
    for(int i = 0; i < path.length; i++)
    {
      buildPath[i] = path[i].toURL();
    }
    return buildPath;
  }

  private Set<String> getNames(Set<JavaClass> testsToRun)
  {
    Set<String> names = new HashSet<String>();
    for(JavaClass clazz : testsToRun)
    {
      names.add(clazz.getName());
    }
    return names;
  }

  JTree getTree()
  {
    return tree;
  }

  JFrame getFrame()
  {
    return frame;
  }

  @Override
  public synchronized void run()
  {
    try
    {      
      Set<JavaClass> testsToRun = index.update();
      if (runTests)
      {
        if (!testsToRun.isEmpty())
        {
          tree.setBackground(workingColor);
          setTitle("Infinitest - Running Tests...");
        }        
        for (Iterator<String> i = getNames(testsToRun).iterator(); i.hasNext() && runTests;)
        {
          String testName = i.next();
          runner.runTest(testName);
        }
      }
      SwingUtilities.invokeAndWait(new Runnable()
      {
        public void run()
        {
          finishTestRun();          
        }
      });
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }   
    
  } 

  private void finishTestRun()
  {
    Date lastRun = new Date(System.currentTimeMillis());
    if (testResults.getTestCaseCount() == 0)
    {
      frame.setTitle(InfinitestSettings.TESTS_PASSED_MSG + lastRun);        
      tree.setBackground(InfinitestSettings.PASSING_COLOR);
    }
    else
    {
      frame.setTitle(InfinitestSettings.FAILURES_DETECTED_MSG + lastRun);
    }
  }
  
  private void setTitle(final String newTitle)
  {
    SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        frame.setTitle(newTitle);        
      }      
    });    
  }

  public static void main(String[] args) throws IOException
  {
    InfinitestMain app = new InfinitestMain(new File(args[0]));
    app.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);    
    app.frame.setVisible(true);
  }

  public boolean shouldRunTests()
  {
    return runTests;
  }

  public void setRunTests(boolean runTests)
  {
    this.runTests = runTests;
  }
}
